package com.tfswx.jkgl.core.dao;

import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.tfswx.jkgl.core.bean.Csdy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author ricky
 * @date 2021/12/10
 */
@Slf4j
public class SqlMapper implements AutoCloseable {

    private final SqlSessionFactory sqlSessionFactory;

    private final Configuration configuration;
    private final LanguageDriver languageDriver;
    private final SqlSession sqlSession;

    public SqlMapper() {
        this(ExecutorType.SIMPLE);
    }

    public SqlMapper(ExecutorType executorType) {
        sqlSessionFactory = SpringUtil.getBean(SqlSessionFactory.class);
        this.configuration = sqlSessionFactory.getConfiguration();
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
        sqlSession = sqlSessionFactory.openSession(executorType);
    }

    public List<BatchResult> flushStatements() {
        return sqlSession.flushStatements();
    }

    public void rollback() {
        sqlSession.rollback();
    }

    public void commit() {
        sqlSession.commit();
    }

    @Override
    public void close() {
        sqlSession.close();
    }

    private String getMsId(String newSql, Class<?> parameterType, Csdy ccpz) {
        String md5 = MD5.create().digestHex(Map.class + newSql + parameterType + JSON.toJSONString(ccpz), StandardCharsets.UTF_8);
        String msId = SqlCommandType.UNKNOWN + "." + md5;
        if (!configuration.hasStatement(msId, false)) {
            SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, newSql, parameterType);
            this.newSelectMappedStatement(msId, sqlSource, ccpz);
        }
        return msId;
    }

    private ResultMap getResultMap(Csdy ccpz) {
        List<Csdy> csdyList = ccpz.getCsdy();
        List<ResultMapping> resultMappings = new ArrayList<>();
        for (Csdy csdy : csdyList) {

            String cslx = csdy.getCslx();
            String csbm = csdy.getCsbm();
            if ("Map".equals(cslx)) {
                ResultMap resultMap = getResultMap(csdy);
                resultMappings.add(new ResultMapping.Builder(this.configuration, csdy.getCsbm(), null, Map.class).nestedResultMapId(resultMap.getId()).build());
            } else if ("List".equals(cslx)) {
                ResultMap resultMap = getResultMap(csdy);
                resultMappings.add(new ResultMapping.Builder(this.configuration, csdy.getCsbm(), null, List.class).nestedResultMapId(resultMap.getId()).build());
            } else {
                resultMappings.add(new ResultMapping.Builder(this.configuration, csbm, csbm, Object.class)
                        // .flags(Collections.singletonList(ResultFlag.ID))
                        .build());
            }
        }
        ResultMap resultMap = new ResultMap.Builder(this.configuration, ccpz.getCsbm() + IdUtil.fastSimpleUUID(), Map.class, resultMappings).build();
        configuration.addResultMap(resultMap);
        return resultMap;
    }

    private void newSelectMappedStatement(String msId, SqlSource sqlSource, Csdy ccpz) {
        MappedStatement.Builder builder = new MappedStatement.Builder(this.configuration, msId, sqlSource, SqlCommandType.UNKNOWN);

        List<ResultMap> resultMaps = new ArrayList<>();
        if (ccpz != null) {
            ResultMap resultMap = getResultMap(ccpz);
            resultMaps.add(resultMap);
        } else {
            ResultMap defaultResultMap = new ResultMap.Builder(configuration, "defaultResultMap", Map.class, new ArrayList<>(0)).build();
            resultMaps.add(defaultResultMap);
        }
        builder.resultMaps(resultMaps);
        this.configuration.addMappedStatement(builder.build());
    }

    public Object selectList(String sql, Object param, Csdy ccpz) {
        Class<?> parameterType = param != null ? param.getClass() : null;
        String newSql = "<script>" + sql + "</script>";
        String msId = this.getMsId(newSql, parameterType, ccpz);
        return sqlSession.selectList(msId, param);
    }

    public Object selectOne(String sql, Object param, Csdy ccpz) {
        Class<?> parameterType = param != null ? param.getClass() : null;
        String newSql = "<script>" + sql + "</script>";
        String msId = this.getMsId(newSql, parameterType, ccpz);
        return sqlSession.selectOne(msId, param);
    }

    public int execute(String sql, Object param) {
        Class<?> parameterType = param != null ? param.getClass() : null;
        String newSql = "<script>" + sql + "</script>";
        String msId = this.getMsId(newSql, parameterType, null);
        int i = sqlSession.update(msId, param);
        log.info("影响行数：{}", i);
        return i;
    }
}
